﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.ComponentModel;
using System.Threading;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace Boomerang.Common.Configuration
{

    public static class ConfigurationManager
    {
        private const string FileName = "BoomerangConfiguration.dat";
        private static string FilePath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        private static Dictionary<String, Organization> organizations = new Dictionary<String, Organization>();
        private static bool configurationLoaded;
        private static FileSystemWatcher watcher;

        static ConfigurationManager()
        {
            Trace.TraceInformation("Initializing Configuration Manager, file = {0}", Path.Combine(FilePath, FileName));
            watcher = new FileSystemWatcher(FilePath, FileName);
            watcher.Changed += new FileSystemEventHandler(FileModified);
            watcher.Deleted += new FileSystemEventHandler(FileModified);
            watcher.Renamed += new RenamedEventHandler(FileModified);
            watcher.EnableRaisingEvents = true;
            Load();
        }

        #region Organization Mgmt Methods

        public static ReadOnlyCollection<Organization> GetAllOrganizations()
        {
            return new ReadOnlyCollection<Organization>(new List<Organization>(organizations.Values));
        }

        public static ReadOnlyCollection<Organization> GetActiveOrganizations()
        {
            //TODO:Implement Filter
            return new ReadOnlyCollection<Organization>(new List<Organization>(organizations.Values));
        }

        public static Organization GetOrganization(string key)
        {

            if (!configurationLoaded) Load();

            if (organizations.ContainsKey(key))
                return organizations[key];
            else
                return null;
        }

        public static void SetOrganization(Organization org)
        {
            Mutex sync = new Mutex(false, FileName);
            sync.WaitOne();
            try
            {
                if (!organizations.ContainsKey(org.Key))
                {
                    Trace.TraceInformation("Modifying existing org {0}:{1}", org, org.Key);
                }
                else
                    Trace.TraceInformation("Creating new org {0}:{1}", org, org.Key);

                organizations[org.Key] = org;

                Save();

                Trace.TraceInformation("Notifying New/Modified organization");
                if (OnConfigurationChange != null) OnConfigurationChange();

            }
            catch (Exception ex)
            {
                Trace.TraceError("Error adding organization {0} \n {1}", ex.Message, ex.StackTrace);
                throw new IOException("Error adding organization", ex);
            }
            finally
            {
                sync.ReleaseMutex();
            }
        }

        public static void RemoveOrganization(Organization org)
        {
            Mutex sync = new Mutex(false, FileName);
            sync.WaitOne();
            try
            {
                if (organizations.ContainsKey(org.Key))
                {
                    Trace.TraceInformation("Removing Organization {0}:{1}", org, org.Key);
                    organizations.Remove(org.Key);
                    Save();
                    if (OnConfigurationChange != null) OnConfigurationChange();
                }
                else
                    Trace.TraceWarning("No organization found when trying to remove {0}:{1}", org, org.Key);
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error removing organization {0} \n {1}", ex.Message, ex.StackTrace);
                throw new IOException("Error removing organization", ex);
            }
            finally
            {
                sync.ReleaseMutex();
            }
        }

        #endregion

        #region Configuration File Management Methods

        public static void Save()
        {
            Stream file = null;

            Mutex sync = new Mutex(false, FileName);
            sync.WaitOne();
            try
            {
                Trace.TraceInformation("Saving file");
                file = File.OpenWrite(Path.Combine(FilePath,FileName));
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(file, ConfigurationManager.organizations);
                Trace.TraceInformation("File saved. {0} Organizations", organizations.Count);
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error saving config file {0} \n {1}", ex.Message, ex.StackTrace);
                throw new IOException("Error saving configuration file", ex);
            }
            finally
            {
                if (file != null) file.Close();
                sync.ReleaseMutex();
            }
        }

        public static void Load()
        {
            Stream file = null;

            Mutex sync = new Mutex(false, FileName);
            sync.WaitOne();

            try
            {

                if (File.Exists(Path.Combine(FilePath, FileName)))
                {
                    Trace.TraceInformation("File Found. Reading contents");
                    file = File.OpenRead(Path.Combine(FilePath, FileName));
                    BinaryFormatter formatter = new BinaryFormatter();
                    organizations = (Dictionary<String, Organization>)formatter.Deserialize(file);
                    Trace.TraceInformation("File Read. Found {0} organizations", organizations.Count);
                }
                else
                    Trace.TraceInformation("File not found, asuming empty list");

                configurationLoaded = true;

            }
            catch (Exception ex)
            {
                Trace.TraceError("Error configuration file {0} \n {1}", ex.Message, ex.StackTrace);
                throw new IOException("Error loading configuration file", ex);
            }
            finally
            {
                if (file != null) file.Close();
                sync.ReleaseMutex();
            }

            Trace.TraceInformation("File Loaded. Send Notification");
            if (OnConfigurationChange != null) OnConfigurationChange();

        }

        static void FileModified(object sender, FileSystemEventArgs e)
        {
            Trace.TraceInformation("File has been modified, reloading");
            Load();
        }

        #endregion

        #region Events


        public static event ConfigurationChangeEventHandler OnConfigurationChange;

        //public static event OrganizationAdded OnOrganizationAdded;

        #endregion
    }

    public delegate void ConfigurationChangeEventHandler();
}
